/**
 * IdGenerator.java 
 * Copyright © 2014-2016, Liu Tao
 * 
 * @author Liu Tao
 * @create 2015年4月10日
 */
package com.pshare.commonid;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.tuple.Pair;
import org.apache.log4j.Logger;

import com.pshare.util.RedisPropertyUtils;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.exceptions.JedisConnectionException;

/**
 * 统一ID生成
 */
public class IdGenerator {

	private static final Logger logger = Logger.getLogger(IdGenerator.class);
	private static final String REDIS_SERVERS = RedisPropertyUtils.getProperty("commonid.redis.servers");
	private static IdGenerator idGenerator;

	/**
	 * JedisPool, luaSha
	 */
	List<Pair<JedisPool, String>> jedisPoolList;
	int retryTimes;

	int index = 0;

	private IdGenerator() {

	}

	public static IdGenerator getInstance() {
		if (idGenerator == null) {
			synchronized (IdGenerator.class) {
				IdGeneratorBuilder builder = new IdGeneratorBuilder();
				String[] servers = REDIS_SERVERS.split(",");
				for (String server : servers) {
					String[] infos = server.split(":");
					builder.addHost(infos[0], infos[1], infos[2]);
				}
				idGenerator = builder.build();
//						.addHost("121.40.73.70", 6379, "16ea0a3f345892d8a51243aa3ab5695ce25a178d")
//						.addHost("127.0.0.1", 7379,
//						"921966051517703de806a15209922df8dbf0365e")
//						.addHost("127.0.0.1", 8379,
//						"20b3e56ec6ebbb7fb242c3d62372d0f7b1a233b7")
//						.build();
			}
		}
		return idGenerator;
	}

	private IdGenerator(List<Pair<JedisPool, String>> jedisPoolList, int retryTimes) {
		this.jedisPoolList = jedisPoolList;
		this.retryTimes = retryTimes;
	}

	public static long getId(String tab, long shardId) {
		return idGenerator.next(tab, shardId);
	}

	static class IdGeneratorBuilder {
		List<Pair<JedisPool, String>> jedisPoolList = new ArrayList();
		int retryTimes = 5;

		public IdGeneratorBuilder addHost(String host, int port, String luaSha) {
			jedisPoolList.add(Pair.of(new JedisPool(host, port), luaSha));
			return this;
		}
		
		public IdGeneratorBuilder addHost(String host, String portStr, String luaSha) {
			jedisPoolList.add(Pair.of(new JedisPool(host, Integer.parseInt(portStr)), luaSha));
			return this;
		}

		public IdGeneratorBuilder retryTimes(int retryTimes) {
			this.retryTimes = retryTimes;
			return this;
		}

		public IdGenerator build() {
			return new IdGenerator(jedisPoolList, retryTimes);
		}
	}

	public long next(String tab) {
		return next(tab, 0);
	}

	public long next(String tab, long shardId) {
		for (int i = 0; i < retryTimes; ++i) {
			Long id = innerNext(tab, shardId);
			if (id != null) {
				return id;
			}
		}
		throw new RuntimeException("Can not generate id!");
	}

	Long innerNext(String tab, long shardId) {
		index++;
		Pair<JedisPool, String> pair = jedisPoolList.get(index % jedisPoolList.size());
		JedisPool jedisPool = pair.getLeft();

		String luaSha = pair.getRight();
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.auth("xxx");
			List<Long> result = (List<Long>) jedis.evalsha(luaSha, 2, tab, "" + shardId);
			long id = buildId(result.get(0), result.get(1), result.get(2), result.get(3));
			return id;
		} catch (JedisConnectionException e) {
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			logger.error("generate id error!", e);
		} finally {
			if (jedis != null) {
				jedisPool.returnResource(jedis);
			}
		}
		return null;
	}

	public static long buildId(long second, long microSecond, long shardId, long seq) {
		long miliSecond = (second * 1000 + microSecond / 1000);
		return (miliSecond << (12 + 10)) + (shardId << 10) + seq;
	}

	public static List<Long> parseId(long id) {
		long miliSecond = id >>> 22;
		// 2 ^ 12 = 0xFFF
		long shardId = (id & (0xFFF << 10)) >> 10;
		long seq = id & 0x3FF;

		List<Long> re = new ArrayList<Long>(4);
		re.add(miliSecond);
		re.add(shardId);
		re.add(seq);
		return re;
	}
}
